﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Utility.Inspector;
using Utility.AssetTools;
using Utility.Math;
using System.Linq;

namespace Utility.DataSystem
{
	public interface IEditorWindow
	{
		bool IsOpen { get; }
	}

	public abstract class SettingObjectEditorWindow<T> : EditorWindow, IEditorWindow
		where T : SettingScriptableObject
	{
		#region struct
		public struct SettingObjectInfo
		{
			public bool isValid;

			public SettingObjectInfo (T obj)
			{
				this.isValid = obj.IsValid;
			}
		}
		#endregion
		#region static
		//copy them to subclass		
		//[MenuItem ("PATH")]
		//protected static void OpenWindow ()
		//{
		//	var window = OpenWindow<TWindow> ();
		//}

		//[UnityEditor.Callbacks.OnOpenAsset (0)]
		//protected static bool OnOpenAsset (int instId, int line)
		//{
		//	return OnOpenAsset<TWindow> (instId, line);
		//}

		//[MenuItem("PATH")]
		protected static TWindow OpenWindow<TWindow> ()
			where TWindow : SettingObjectEditorWindow<T>
		{
			var window = GetWindow<TWindow> ();
			window.Show ();
			return window;
		}

		//[UnityEditor.Callbacks.OnOpenAsset(0)]
		protected static bool OnOpenAsset<TWindow> (int instId, int line)
			where TWindow : SettingObjectEditorWindow<T>
		{
			var obj = EditorUtility.InstanceIDToObject (instId);
			T setting = obj as T;
			if (setting != null)
			{
				var window = OpenWindow<TWindow> ();
				window.Setting = setting;
				return true;
			}
			return false;
		}

		#endregion

		protected virtual bool CanDisable => false;
		protected virtual bool CanDisableSetting (T settingObj) => true;
		protected abstract GUIContent TitleContent { get; }
		protected abstract string SettingPath { get; }

		public bool IsOpen { get; private set; }

		protected virtual void OnEnable ()
		{
			IsOpen = true;
			titleContent = TitleContent;
			CacheSettingObjects ();
			Setting = Setting ?? settingObjects.FirstOrDefault ();
		}

		protected virtual void OnDisable ()
		{
			IsOpen = false;
		}

		public T Setting { get; protected set; }

		protected static T[] settingObjects;
		protected static Dictionary<T, SettingObjectInfo> infoDict = new Dictionary<T, SettingObjectInfo> ();

		string newSettingName;
		Vector2 scrollPos = default;
		bool toggleSetting = true;

		private void OnGUI ()
		{
			using (ScrollViewScope.New (ref scrollPos))
			{
				//playmode refresh
				if (Application.isPlaying)
				{
					using (HorizontalScope.New (FlexibleTypes.Begin))
					{
						if (EditorGUILayoutEX.RefreshButton ())
						{
							Setting.InitSetting ();
							Debug.Log ("Init setting: " + Setting.name);
						}
					}
				}

				//object field
				GUILayoutUtility.GetRect (0, 2);
				Rect rect = GUILayoutUtility.GetRect (0, EditorGUIUtility.singleLineHeight);
				toggleSetting = EditorGUI.Foldout (rect.ResizeWidth (12), toggleSetting, "");
				Setting = (T)EditorGUI.ObjectField (rect.ShrinkLeft (12), "设定文件", Setting, typeof (T), false);

				if (toggleSetting)
				{
					using (HorizontalScope.New (FlexibleTypes.Begin))
					{
						newSettingName = EditorGUILayout.TextField (newSettingName);
						using (GUIEnableScope.New (!string.IsNullOrEmpty (newSettingName)))
						{
							//create
							if (GUILayout.Button ("新建", GUILayout.Width (50)))
							{
								Setting = CreateSetting (newSettingName);
								newSettingName = "";
								CacheSettingObjects ();

								SettingCenter.LoadSettings (true);
							}
							//rename
							using (GUIEnableScope.New (Setting != null))
							{
								if (GUILayout.Button ("重命名", GUILayout.Width (50)))
								{
									Setting.name = newSettingName;
									AssetDatabase.RenameAsset (AssetDatabase.GetAssetPath (Setting), newSettingName);
									newSettingName = "";
									CacheSettingObjects ();

									SettingCenter.LoadSettings (true);
								}
							}
						}
						//dulicate
						using (GUIEnableScope.New (Setting != null))
						{
							if (GUILayout.Button ("复制", GUILayout.Width (50)))
							{
								string path = AssetDatabase.GetAssetPath (Setting);
								string newPath = AssetDatabase.GenerateUniqueAssetPath (path);

								var newAsset = Instantiate (Setting);
								AssetDatabase.CreateAsset (newAsset, newPath);
								newSettingName = "";
								CacheSettingObjects ();
								SettingCenter.LoadSettings (true);
								Setting = newAsset;

								Debug.Log ("New asset created: " + newPath);
							}
						}
						//delete
						using (GUIEnableScope.New (Setting != null))
						{
							if (GUILayout.Button ("删除", GUILayout.Width (50)))
							{
								string msg = $"确认删除文件: {Setting.name}";
								if (EditorUtility.DisplayDialog ("是否删除文件？", msg, "删除", "取消"))
								{
									string path = AssetDatabase.GetAssetPath (Setting);
									if (AssetDatabase.DeleteAsset (path))
									{
										CacheSettingObjects ();
										Setting = null;
										Debug.Log ("Asset deleted: " + path);

										SettingCenter.LoadSettings (true);
									}
								}
							}
						}
					}
					//scroll
					DrawSelectionScroll (EditorGUIUtility.singleLineHeight * 5);
					//toggle buttons
					if (CanDisable)
					{
						using (HorizontalScope.New ())
						{
							if (GUILayout.Button ("激活全部"))
							{
								foreach (var obj in settingObjects)
								{
									obj.enabled = true;
									EditorUtility.SetDirty (obj);
								}
							}
							if (GUILayout.Button ("取消全部"))
							{
								foreach (var obj in settingObjects)
								{
									obj.enabled = false;
									EditorUtility.SetDirty (obj);
								}
							}
						}
					}
				}

				DrawAboveSeparatorLine ();
				EditorGUILayoutEX.SeparatorLine ();

				//draw setting
				if (Setting != null)
				{
					EditorGUI.BeginChangeCheck ();
					DrawSetting ();
					if (EditorGUI.EndChangeCheck ())
					{
						EditorUtility.SetDirty (Setting);
					}
					infoDict[Setting] = new SettingObjectInfo (Setting);
				}
				else
				{
					EditorGUILayout.HelpBox ("选择或新建一个设定文件", MessageType.Warning);
				}
			}
		}

		protected virtual T CreateSetting (string name)
		{
			return GetOrCreate<T> (SettingPath, name);
		}

		protected virtual void DrawAboveSeparatorLine () { }
		protected abstract void DrawSetting ();

		public void CacheSettingObjects ()
		{
			settingObjects = Resources.LoadAll<T> ("");
			System.Array.Sort (settingObjects, (a, b) => EditorUtility.NaturalCompare (a.name, b.name));
			infoDict = (from obj in settingObjects
						let info = new SettingObjectInfo (obj)
						select (obj, info)).ToDictionary (pair => pair.obj, pair => pair.info);
		}

		public void SetSettingObjectsDirty ()
		{
			foreach (var setting in settingObjects)
			{
				EditorUtility.SetDirty (setting);
			}
		}

		//refresh icon
		static Texture2D _icon_refresh;
		static Texture2D Icon_Refresh => _icon_refresh ?? (_icon_refresh = (Texture2D)EditorGUIUtility.Load ("RotateTool"));

		static GUIStyle _style_item;
		static GUIStyle Style_Item => _style_item ?? (_style_item = new GUIStyle ("in thumbnailselection")
		{
			padding = new RectOffset (5, 5, 0, 0),
			alignment = TextAnchor.MiddleLeft,
			stretchWidth = true
		});

		static GUIStyle _style_scroll;
		static GUIStyle Style_Scroll => _style_scroll ?? (_style_scroll = new GUIStyle () { margin = new RectOffset (5, 5, 0, 0) });

		Vector2 scrollPos_selection;
		void DrawSelectionScroll (float height)
		{
			using (HorizontalScope.New ())
			{
				//label
				EditorGUILayout.LabelField ("文件列表");
				GUILayout.FlexibleSpace ();
				//refresh
				if (GUILayout.Button (Icon_Refresh, GUILayout.Height (EditorGUIUtility.singleLineHeight)))
				{
					CacheSettingObjects ();
				}
			}
			//bg
			Rect rect = GUILayoutUtility.GetRect (0, 0).ResizeHeight (height).ShrinkWidth (10);
			GUI.Box (rect, "", EditorStyles.textArea);

			float w_toggle = 16;
			scrollPos_selection = EditorGUILayout.BeginScrollView (scrollPos_selection, Style_Scroll, GUILayout.Height (height));
			{
				//none
				{
					bool selected = Setting == null;
					GUIContent content = new GUIContent ("     None", (Texture)null);
					var rect_item = GUILayoutUtility.GetRect (0, EditorGUIUtility.singleLineHeight).ShrinkLeft (CanDisable ? w_toggle : 0);

					if (GUI.Button (rect_item, "", GUIStyle.none))
					{
						Setting = null;
					}
					GUI.Toggle (rect_item, selected, content, Style_Item);
				}
				//list
				foreach (var obj in settingObjects)
				{
					if (!obj)
					{
						CacheSettingObjects ();
						break;
					}
				}
				foreach (var obj in settingObjects)
				{
					bool selected = obj == Setting;
					string title = obj.name;
					if (obj is ISettingObjectShowname showname)
					{
						title += $" {showname.Showname}";
					}
					GUIContent content = new GUIContent (title, AssetPreview.GetMiniThumbnail (obj));

					var rect_line = GUILayoutUtility.GetRect (0, EditorGUIUtility.singleLineHeight);
					var rect_item = rect_line.ShrinkLeft (CanDisable ? w_toggle : 0);

					//item btn
					if (GUI.Button (rect_item, "", GUIStyle.none))
					{
						Setting = obj;
					}

					//item					
					bool isValid = infoDict.TryGetValue (obj, out var info) ? info.isValid : false;
					using (GUIColorScope.New (!isValid, new Color (1f, 0.5f, 0.5f)))
					{
						GUI.Toggle (rect_item, selected, content, Style_Item);
					}

					//toggle
					Rect rect_toggle = rect_line.DockLeft (w_toggle);
					bool enable_old = obj.enabled;
					if (CanDisable && CanDisableSetting (obj))
					{
						obj.enabled = GUI.Toggle (rect_toggle, obj.enabled, "");
					}
					else
					{
						obj.enabled = true;
					}
					if (obj.enabled != enable_old)
					{
						EditorUtility.SetDirty (obj);
					}
				}
				GUILayoutUtility.GetRect (0, 3);
			}
			EditorGUILayout.EndScrollView ();
		}



		#region utility
		protected TSettingObject GetOrCreate<TSettingObject> (string folderPath, string name, bool forceCreate = false)
			where TSettingObject : SettingScriptableObject
		{
			if (AssetDatabaseEX.GetOrCreateScriptableObject<TSettingObject> (folderPath, name, out var settingObj, forceCreate))
			{
				settingObj.OnCreate ();
			}
			return settingObj;
		}

		protected TSettingObject GetOrCreateCompanionSettingObject<TSettingObject> (SettingScriptableObject mainSettingObject, string folderPath)
			where TSettingObject : SettingScriptableObject
		{
			return GetOrCreate<TSettingObject> (folderPath, mainSettingObject.name);
		}

		protected TSettingObject CompanionSettingObjectField<TSettingObject> (string label, TSettingObject settingObject, string folderPath)
			where TSettingObject : SettingScriptableObject
		{
			TSettingObject obj;
			using (HorizontalScope.New ())
			{
				obj = EditorGUILayoutEX.ObjectField (label, settingObject, false);
				if (GUILayout.Button ("关联", GUILayout.MaxWidth (40)))
				{
					obj = GetOrCreateCompanionSettingObject<TSettingObject> (Setting, folderPath);
				}
			}
			return obj;
		}



		protected TSubObject GetOrCreateSubObject<TSubObject> (string name, T mainObj = null)
			where TSubObject : ScriptableObject
		{
			mainObj = mainObj ?? Setting;

			var subObj = AssetDatabaseEX.FindSubAsset<TSubObject> (mainObj, name);
			if (subObj == null)
			{
				subObj = CreateInstance<TSubObject> ();
				subObj.name = name;
				AssetDatabase.AddObjectToAsset (subObj, mainObj);
				AssetDatabaseEX.ReimportAsset (mainObj);
			}
			return subObj;
		}
		#endregion
	}
}